module necrophilia.helpers.Callback;
import necrophilia.Exceptions;

/** 
	Helper template building structure, that can store either a delegate
	or a function. It can be used, to improve a flexibility. So we han 
	set function/delegate to profile something without overthrowing
	what form should it take.
	Params:
	Returns= is a type stored function or delegate will return
	T...= are args of function/delegate
	Examples:
	Let's say we want our Callback to hold:
	void function/delegate (int,float)
	--------------------------
	void test(int a, float b){
		// something
	}
	
	alias Callback!(void, int, float) myCallback;
	myCallback c;
	c.set(&test);
	c.call(1,0.2);
	-------------------------
*/
struct Callback (Returns, T ... ) {
	private union Func{
		Returns function  (T) func;
		Returns delegate (T) del;
	}
	
	private enum OperationType{
		notSet=0,
		func,
		del
	}
	
	private Func _func;
	private OperationType _type;
	
	/**
		Sets callback as a function
		Params:
		func=pointer to a function which should be called when colling callback
	*/
	void set(Returns function  (T) func){
		_func.func=func;
		_type=OperationType.func;
	}
	
	/**
		Sets callback as a delegate
		Params:
		del=pointer to a delegate which should be called when colling callback
	*/
	void set(Returns delegate (T) del){
		_func.del=del;
		_type=OperationType.del;
	}
	
	/**
		Calls function/delegate
		Params:
		args=args you would normally put inside function/delegate
		Returns:
		what would normally function/delegate return
		Throws:
		CallbackException when Callback was not set
	*/
	Returns call(T args){
		switch(_type){
			case OperationType.func: return _func.func(args);
			case OperationType.del: return _func.del(args);
			case OperationType.notSet: throw new CallbackException("Callback not initialized");
		}
	}
}

/** 
	A bit profiled Callback to store more sofisticated functions/delegates
	used for interpolation. Those functions are normally done like
	DataType function (TimeType, DataType[] ...); 
	So such things are produced by InterpolationCallback (impossible
	to do with normaln callback). 
	Params:
	Returns= is a type stored function or delegate will return
	T= first callback papram (TimeType from example)
	I = DataType from example
	Examples:
	Let's look at a simple examle
	--------------------------
	class Foo {
		int test(float ble, int[] s...){
			assert(ble==0.f);
			int result;
			foreach(a;s){
				result+=a;
			}
			return result;
		}
	}
	
	alias InterpolationCallback!(int, float, int) myCallback;
	myCallback c;
	Foo foo= new Foo();
	c.set(&foo.test);
	assert(c.call(0.f, 1,2,3)==6);
	--------------------------
	
*/
struct InterpolationCallback (Returns, T, I) {
	private union Func{
		Returns function  (T, I[] ...) func;
		Returns delegate (T, I[] ...) del;
	}
	
	private enum OperationType{
		notSet=0,
		func,
		del
	}
	
	private Func _func;
	private OperationType _type;
	
	/**
		Sets callback as a function
		Params:
		func=pointer to a function which should be called when colling callback
	*/
	void set(Returns function  (T, I[] ...) func){
		_func.func=func;
		_type=OperationType.func;
	}
	
	/**
		Sets callback as a delegate
		Params:
		del=pointer to a delegate which should be called when colling callback
	*/
	void set(Returns delegate (T, I[] ...) del){
		_func.del=del;
		_type=OperationType.del;
	}
	
	/**
		Calls function/delegate
		Params:
		args=args you would normally put inside function/delegate
		Returns:
		what would normally function/delegate return
		Throws:
		CallbackException when Callback was not set
	*/
	Returns call(T arg, I[] args ...){
		switch(_type){
			case OperationType.func: return _func.func(arg,args);
			case OperationType.del: return _func.del(arg,args);
			case OperationType.notSet: throw new CallbackException("Callback not initialized");
		}
	}
}

// ---------------------------------------- Unittest ----------------------------------

version (Unittest){
	void test(int a, float b){ /* nothing to be done */ }
	
	class Foo {
		
		int test(float ble, int[] s...){
			assert(ble==0.f);
			int result;
			foreach(a;s){
				result+=a;
			}
			return result;
		}
		
	}}

unittest {
	version (Unittest) {
		{
			alias Callback!(void, int, float) myCallback;
			myCallback c;
			c.set(&test);
			c.call(1,0.2);
		}
		
		{
			alias InterpolationCallback!(int, float, int) myCallback;
			myCallback c;
			Foo foo= new Foo();
			c.set(&foo.test);
			assert(c.call(0.f, 1,2,3)==6);
		}
	}
}